package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.dto.PageResult;
import cn.itcast.hotel.dto.RequestParams;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tdunning.math.stats.Sort;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Map;

@Service
@Slf4j
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    private boolean  scoreCounter =true;
    private boolean priceCounter=true;

    @Autowired
    RestHighLevelClient client;

    /**
     * 条件搜索
     *
     * @param params
     * @return
     */
    @Override
    public PageResult search(RequestParams params) {

        //构建查询对象所需索引库
        SearchRequest request = new SearchRequest("hotel");
        //构建查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //输入框
        String key = params.getKey();
        if (StringUtils.isNotBlank(key)) {
            boolQuery.must(
                    QueryBuilders.matchQuery("all", key)
            );
        } else {
            boolQuery.must(
                    QueryBuilders.matchAllQuery()
            );
        }

        //条件过滤
        //品牌条件
        String brand = params.getBrand();
        if (StringUtils.isNotBlank(brand)) {
            boolQuery.must(
                    QueryBuilders.termQuery("brand", brand)
            );
        }
        //城市条件
        String city = params.getCity();
        if (StringUtils.isNotBlank(city)) {
            boolQuery.must(
                    QueryBuilders.termQuery("city", city)
            );
        }
        //星级条件
        String starName = params.getStarName();
        if (StringUtils.isNotBlank(starName)) {
            boolQuery.must(
                    QueryBuilders.termQuery("starName", starName)
            );
        }
        //价格条件
        Integer maxPrice = params.getMaxPrice();
        Integer minPrice = params.getMinPrice();
        if (maxPrice != null && minPrice != null) {
            boolQuery.must(
                    QueryBuilders.rangeQuery("price")
                            .gt(minPrice)
                            .lte(maxPrice)
            );
        }

        //位置过滤
        String location = params.getLocation();
        if (StringUtils.isNotBlank(location)){
            request.source().sort(
                    SortBuilders.geoDistanceSort("location",new GeoPoint(location))
                            //升序排列
                            .order(SortOrder.ASC)
                            //单位 km
                            .unit(DistanceUnit.KILOMETERS)
            );
        }
        //排序规则  default score  price
        String sortBy = params.getSortBy();
        if (sortBy.equals("score")){
            if (scoreCounter){
                request.source().sort(
                        "score",SortOrder.DESC
                );
                scoreCounter = false;
            }else {
                request.source().sort(
                        "score",SortOrder.ASC
                );
                scoreCounter = true;
            }

        }
        if(sortBy.equals("price")){
            if (priceCounter){
                request.source().sort(
                        "price",SortOrder.ASC
                );
                priceCounter = false;
            }else {
                request.source().sort(
                        "price",SortOrder.DESC
                );
                priceCounter = true;
            }

        }

        //高亮
        request.source().highlighter(
                SearchSourceBuilder.highlight()
                        .field("name")
                        .requireFieldMatch(false)
        );

        //广告加权
        //使用boolQuery的结果再进行算分加权
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                });

        //封装查询条件
        request.source().query(functionScoreQuery);
        //设置分页
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page - 1) * size).size(size);
        //定义结果参数
        long total = 0;
        ArrayList<HotelDoc> hotelDocs = new ArrayList<>();
        //执行查询
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //总记录数
            total = response.getHits().getTotalHits().value;
            //获取文档数组
            SearchHit[] hits = response.getHits().getHits();

            //遍历文档数据 去除数据
            for (SearchHit hit : hits) {
                String json = hit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if (!CollectionUtils.isEmpty(highlightFields)){
                    //获取highlightField
                    HighlightField highlightField = highlightFields.get("name");
                    if (highlightField != null){
                        //获取值
                        Text[] fragments = highlightField.getFragments();
                        if (fragments.length >0){
                            String name = StringUtils.join(fragments);
                            //覆盖结果
                            hotelDoc.setName(name);
                        }
                    }

                }


                Object[] sortValues = hit.getSortValues();
                if (sortValues.length > 0){
                    Object distance = sortValues[0];
                    //覆盖结果
                    hotelDoc.setDistance(distance);
                }
                //将取出的数据存入结果集中
                hotelDocs.add(hotelDoc);
            }
            return PageResult.builder().total(total).hotels(hotelDocs).build();

        } catch (IOException e) {
            log.error("search error message:{}", e.getMessage());
        }
        //分装结果对象 返回
        return new PageResult();
    }
}
